'''
'''

# Standard
import logging
# Related
# Local
import errors
import vertex_map
import al.query_services.scene
import al.query_services.layer
import moopy.al.query.item


logger = logging.getLogger('moopy')

def get_id_by_index(index, item_type=None):
    ''''''
    if item_type is not None:
        return al.query_services.scene.get_item_id(index, item_type)
    else:
        return al.query_services.scene.get_item_id(index)

def get_item_by_name(name):
    ''''''
    raise NotImplementedError()

def get_item_by_id(item_id):
    ''''''
    raise NotImplementedError()

def get_item_class_from_id(item_id):
    '''Return the Class of the object type.'''
    type_switch = {
        'imageMap':ImageMap,
        'mesh':Mesh,
    }

    item_type = al.query_services.scene.get_item_type(item_id)

    if not type_switch.has_key(item_type):
        raise NotImplementedError()
    else:
        return type_switch[item_type]

class Item(object):
    '''The base item. Not indended for direct use, but feel free to
    inherit it.
    '''
    _mtype = None
    _mtype_label = None

    def __init__(self, item_id, scene, *args, **kwargs):
        '''
        @param item_id: The item id that this object will represent.
        
        @param scene: The scene of the object.
        '''
        super(Item, self).__init__(*args, **kwargs)

        self._channels = None
        self._item_id = item_id
        self._scene = scene
        self._visibility = None

        # If this fails, we can assume the item_id does not exist.
        try:
            self._name = al.query_services.scene.get_item_name(item_id)
        except:
            errors.ItemIDNonExistantError('ItemID: %s' % item_id)

    def __repr__(self):
        '''
        '''
        return "<class 'moopy.item.Item', name:%s>" % (self.name)

    def __str__(self):
        '''
        '''
        return 'Item, Name %s' % (self.name)
    
    def __eq__(self, other):
        ''''''
        if isinstance(other, Item):
            return self._item_id == other._item_id
        else:
            return NotImplemented
    
    def __ne__(self, other):
        ''''''
        equal = self.__eq__(other)
        if equal is NotImplemented:
            return equal
        else:
            return not equal

    def _get_channels(self):
        ''''''
        raise NotImplementedError()

    channels = property(
        _get_channels,
        doc=''' The channels object for this object.
        '''
    )

    def _get_item_id(self):
        ''''''
        return self._item_id

    item_id = property(
        _get_item_id,
        doc=''' The item ID.
        '''
    )

    def _get_mtype(self):
        ''''''
        return self._mtype

    mtype = property(
        _get_mtype,
        doc=''' The string type, as found in modo.
        '''
    )

    def _get_mtype_label(self):
        ''''''
        return self._mtype_label

    mtype_label = property(
        _get_mtype_label,
        doc=''' The human readable label of the item type.
        '''
    )

    def _get_name(self):
        ''''''
        return self._name

    def _set_name(self, value):
        ''''''
        raise NotImplementedError()

    name = property(
        _get_name,
        _set_name,
        doc='''The name of the item in modo.
        '''
    )
    
    def _get_parent(self):
        ''''''
        raise NotImplementedError()
    
    def _set_parent(self, value):
        ''''''
        raise NotImplementedError()
    
    parent = property(
        _get_parent,
        _set_parent,
        doc='''
        '''
    )

    def _get_visibility(self):
        ''''''
        raise NotImplementedError()

    def _set_visibility(self, value):
        ''''''
        raise NotImplementedError()

    visibility = property(
        _get_visibility,
        _set_visibility,
        doc='''The visibility of the item, not to be confused with L{the alpha
        of the item. <self.channels.alpha>}.
        '''
    )

    def add_child(self, item):
        '''Make this item the parent of the given item.
        '''
        raise NotImplementedError()

    def add_children(self, items):
        '''Make this item the parent of the given items
        '''
        map(self.add_child, items)

    def instance(self, name=None):
        '''
        '''
        raise NotImplementedError()

    @classmethod
    def new(cls, scene, name=None, parent=None):
        '''Create a new item and return that object.'''
        # Create the item.
        al.commands.item.create(type=cls._mtype)
        # Get the item id
        item_id = al.query_services.scene.get_scene_selection(cls._mtype)
        # Create the instance
        item = cls(item_id, scene)
        # Rename it if needed.
        if name is not None:
            item.name = name
        if parent is not None:
            item.parent = parent
        # Return it.
        return item

    def rename(self, name):
        '''
        '''
        raise NotImplementedError()

    def remove_child(self, adopting_parent=None):
        '''Remove a child of this object.

        @param adopting_parent: The new parent object, if any. If this is none,
        the scene root is used.
        '''
        raise NotImplementedError()

    def select(self, add=False):
        '''
        '''
        raise NotImplementedError()

class Clip(Item):
    ''''''

    _mtype = 'clip'
    _mtype_label = 'Clip'

    def __init__(self, item_id, *args, **kwargs):
        ''''''
        super(Clip, self).__init__(item_id, *args, **kwargs)

        if al.query_services.scene.get_item_type(item_id) != self._mtype:
            raise errors.ItemIDNotClassTypeError(
                'ItemID:%s is not an %s Item.' % (item_id, self._mtype_label))

class ImageMap(Item):
    '''An image map, as seen in the shader tree.'''

    _mtype = 'imageMap'
    _mtype_label = 'Image Map'

    def __init__(self, item_id, index=None, *args, **kwargs):
        ''''''
        super(ImageMap, self).__init__(item_id, *args, **kwargs)

        self._index = index

        if al.query_services.scene.get_item_type(item_id) != self._mtype:
            raise errors.ItemIDNotClassTypeError(
                'ItemID:%s is not an %s Item.' % (item_id, self._mtype_label))

    def __repr__(self):
        '''
        '''
        return "<class 'moopy.item.ImageMap', name:'%s'>" % (self.name)

    def __str__(self):
        '''
        '''
        return "Image Map, Name '%s'" % (self.name)

    def _get_clip_path(self):
        ''''''
        return al.query_services.layer.get_texture_clipfile(self.index)

    def _set_clip_path(self, value):
        ''''''
        raise NotImplementedError()

    clip_path = property(
        _get_clip_path,
        _set_clip_path,
        doc='''A path of the clipfile (if any) that this instance contains.

        @important: This is an expensive property to get, if index is None.
        This is because the layerservice query currently (and unfortunately)
        required to get the clipfile information, requires the index. To
        understand why needing the index could be expensive, read the docs
        of L{<self.index>}
        '''
    )

    def _get_index(self):
        ''''''
        return al.query_services.layer.get_texture_index(self._item_id)

    def _set_index(self, value):
        ''''''
        raise NotImplementedError()

    index = property(
        _get_index,
        _set_index,
        doc='''The index of the texture in the shader tree.

        @important: that this object often is created without knowledge of
        the index. So if we don't have it, we must loop through all textures
        until we run into this image map's ID. A very expensive procedure.
        '''
    )

class PolyRender(Item):
    ''''''

    _mtype = 'polyRender'
    _mtype_label = 'Poly Render'

    def __init__(self, item_id, *args, **kwargs):
        ''''''
        super(PolyRender, self).__init__(item_id, *args, **kwargs)

        if al.query_services.scene.get_item_type(item_id) != self._mtype:
            raise errors.ItemIDNotClassTypeError(
                'ItemID:%s is not an %s Item.' % (item_id, self._mtype_label))

class PolyRender(Item):
    ''''''

    _mtype = 'polyRender'
    _mtype_label = 'Poly Render'

    def __init__(self, item_id, *args, **kwargs):
        ''''''
        super(PolyRender, self).__init__(item_id, *args, **kwargs)

        if al.query_services.scene.get_item_type(item_id) != self._mtype:
            raise errors.ItemIDNotClassTypeError(
                'ItemID:%s is not an %s Item.' % (item_id, self._mtype_label))

class SceneItem(Item):
    '''The base scene item. Not indended for direct use, but feel free to
    inherit it.
    '''

    def __init__(self, *args, **kwargs):
        '''
        '''
        super(SceneItem, self).__init__(*args, **kwargs)

    def __repr__(self):
        '''
        '''
        return "<class 'moopy.item.SceneItem', item_id:%s, name:%s>" % (
            self._item_id, self._name)

    def __str__(self):
        '''
        '''
        return 'Scene Item, Name %s' % (self._name)

class Locator(SceneItem):
    ''''''

    _mtype = 'locator'
    _mtype_label = 'Locator'

    def __init__(self, item_id, *args, **kwargs):
        ''''''
        super(Locator, self).__init__(item_id, *args, **kwargs)

        if al.query_services.scene.get_item_type(item_id) != self._mtype:
            raise errors.ItemIDNotClassTypeError(
                'ItemID:%s is not an %s Item.' % (item_id, self._mtype_label))

class Mesh(SceneItem):
    ''''''

    _mtype = 'mesh'
    _mtype_label = 'Mesh'

    def __init__(self, item_id, *args, **kwargs):
        ''''''
        super(Mesh, self).__init__(item_id, *args, **kwargs)

        if al.query_services.scene.get_item_type(item_id) != self._mtype:
            raise errors.ItemIDNotClassTypeError(
                'ItemID:%s is not an %s Item.' % (item_id, self._mtype_label))

    def __repr__(self):
        '''
        '''
        return "<class 'moopy.item.Mesh', layer_id:%s, name:'%s'>" % (
            self._layer_id, self._name)

    def __str__(self):
        '''
        '''
        return 'Mesh Item, Name %s' % (self._name)

    def _get_edges(self):
        ''''''
        raise NotImplementedError()

    edges = property(
        _get_edges,
        doc='''
        '''
    )

    def _get_polygons(self):
        ''''''
        raise NotImplementedError()

    polygons = property(
        _get_polygons,
        doc='''
        '''
    )

    def _get_vertices(self):
        ''''''
        raise NotImplementedError()

    vertices = property(
        _get_vertices,
        doc='''
        '''
    )

class ShaderItem(Item):
    '''The base scene item. Not indended for direct use, but feel free to
    inherit it.
    '''

    def __init__(self, *args, **kwargs):
        '''
        '''
        super(SceneItem, self).__init__(*args, **kwargs)

    def __repr__(self):
        '''
        '''
        return "<class 'moopy.item.ShaderItem', item_id:%s, name:%s>" % (
            self._item_id, self._name)

    def __str__(self):
        '''
        '''
        return 'Shader Item, Name %s' % (self._name)

class ItemCollection(object):
    '''A collection of items.'''

    def __init__(self, ids=None, *args, **kwargs):
        '''
        '''
        super(ItemCollection, self).__init__(*args, **kwargs)

        if ids is not None:
            #: An iterable object of string IDs.
            self._ids = set(ids)
            #: This serves as cache for the items contained in this class.
            #: That way there is no need to continually create new objects for
            #: each item.
            self._item_cache = dict.fromkeys(ids)
        else:
            self._ids = []
            self._item_cache = {}

    def __repr__(self):
        '''
        '''
        return "<class 'moopy.item.ItemCollection', len:%s>" % (self.__len__())

    def __str__(self):
        '''
        '''
        return 'Item Collection, len:%s' % (self.__len__())

    def __len__(self):
        ''''''
        return len(self._ids)

    def __getitem__(self, index):
        ''''''
        try:
            item_id = self._ids[index]
            if self._item_cache.has_key(item_id):
                return self._item_cache[item_id]
            else:
                item = get_item_class_from_id(item_id)(item_id)
                self._item_cache[item_id] = item
                return item
        except IndexError:
            raise IndexError()

    def add_collection(self, collection):
        '''Add a collection to this collection, ignoring any duplicates.'''

        self._ids = list(set(collection._ids).union(self._ids))
        self._item_cache.update(collection._item_cache)

    def add_item(self, item):
        '''Add an item to this collection.'''

        self._ids += item.item_id
        self._item_cache[item.item_id] = item

    def add_selected(self, item_type=None):
        '''Add the selected items to this collection, ignoring any duplicates.
        '''

        if item_type is None:
            item_ids = moopy.al.query.item.get_selected_item_ids(
                item_type._mtype)
        else:
            item_ids = moopy.al.query.item.get_selected_item_ids(
                item_type._mtype)

        if item_ids is not None:
            self._ids = list(set(item_ids).union(self._ids))

    def filter_type(self, item_type):
        '''Return a new item collection based on any item matching the type
        given to this method.'''

        matching_item_ids = []

        for item_id in self._ids:
            if (al.query_services.scene.get_item_type(item_id) == 
                item_type._mtype):
                matching_item_ids.append(item_id)

        return ItemCollection(matching_item_ids)

